package de.farbstreich.tozen2.client.board;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;

import de.farbstreich.tozen2.common.AePlayWave;
import de.farbstreich.tozen2.common.Tools;

/**
 * @author martin TozenBoard shows a playing field holding the tiles from
 *         {@link TozenBoard.cs104.tozen2.jabb.TileEnum}. Moving tiles is able
 *         using drag&drop. Register an ActionListener with
 *         {@link TozenBoard#addActionListener(ActionListener)} to react if the
 *         user tries a move per drag&drop
 */
public class TozenBoard extends JPanel {

	/**
	 * The width/height of a field in pixel
	 */
	private static final int FIELD_SIZE = 38;
	/**
	 * The width of the lines between fields
	 */
	private static final int LINE_SIZE = 1;
	/**
	 * The zoom factor for a tile during dragging.
	 */
	private static final double DRAGGING_ZOOM = 1.2;
	/**
	 * The weight/height of the whole board
	 */
	private static final int BOARD_SIZE = LINE_SIZE * 13 + FIELD_SIZE * 12;

	private static final long serialVersionUID = 1L;
	

	/**
	 * @author martin A Tile on {@link TozenBoard}
	 */
	public class Tile {
		int color;
		TileEnum tile;
		Shape shape;

		/**
		 * @param color
		 * @param tile
		 * @throws NullPointerException
		 *             If the color is not in the by @link
		 *             TozenBoard.cs104.tozen2.jabb.TileEnum defined range
		 */
		public Tile(int color, TileEnum tile) throws NullPointerException {
			if (color > TileEnum.NUMBER_OF_COLORS - 1)
				throw new NullPointerException("There maximum of Players ist "
						+ TileEnum.NUMBER_OF_COLORS);
			this.color = color;
			this.tile = tile;
		}

		/**
		 * @return the image of this tile
		 */
		public BufferedImage getImage() {
			if(discoMode == true)
				return this.tile.getImage((int)Math.round((Math.random()*4) % 3));
			return this.tile.getImage(this.color);
		}

		/**
		 * @return the shadow image of this tile
		 */
		public BufferedImage getShadowImage() {
			return this.tile.getShadowImage();
		}

	}

	/**
	 * @author martin Represents a move on the playing field
	 */
	public class Move {
		/**
		 * The origin of the move
		 */
		public Point origin;
		/**
		 * The target of the move
		 */
		public Point target;

		/**
		 * @param o
		 *            The origin of the move
		 * @param t
		 *            The target of the move
		 */
		public Move(Point o, Point t) {
			this.origin = o;
			this.target = t;
		}
	}

	/**
	 * @author martin Holds the MouseActions for the map
	 */
	private class TozenBoardMouseAdabter extends MouseAdapter {

		@Override
		public void mousePressed(MouseEvent e) {
			if (parent.draggingTile == null && parent.moveable == true) {

				Point field = parent.getFieldUnderPosition(e.getX(), e.getY());
				if (parent.board[field.x][field.y] != null
						&& (parent.movingColor == -1 || parent.movingColor == parent.board[field.x][field.y].color)) {
					parent.draggingTile = parent.board[field.x][field.y];
					parent.draggingTileOrigin = field;
					parent.board[field.x][field.y] = null;
					parent.setCursor(new Cursor(Cursor.MOVE_CURSOR));
				}
				parent.repaint();
			}
		}

		public void mouseMoved(MouseEvent e) {
			if (parent.draggingTile == null) {
				Point field = parent.getFieldUnderPosition(e.getX(), e.getY());
				if (field.x >= 0
						&& field.x <= 11
						&& field.y >= 0
						&& field.y <= 11
						&& parent.board[field.x][field.y] != null
						&& parent.moveable == true
						&& parent.getCursor() != new Cursor(Cursor.HAND_CURSOR)
						&& (parent.movingColor == -1 || parent.movingColor == parent.board[field.x][field.y].color)) {
					parent.setCursor(new Cursor(Cursor.HAND_CURSOR));
				} else if (parent.getCursor() != new Cursor(
						Cursor.DEFAULT_CURSOR))
					parent.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
			}
		}

		@Override
		public void mouseDragged(MouseEvent e) {
			if (parent.draggingTile != null)
				parent.repaint();
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			if (parent.draggingTile != null) {
				Point field = parent.getFieldUnderPosition(e.getX(), e.getY());
				parent.draggingTile.tile.playSound();
				parent.board[parent.draggingTileOrigin.x][parent.draggingTileOrigin.y] = parent.draggingTile;
				parent.lastTry = new Move(parent.draggingTileOrigin, field);
				parent.draggingTile = null;
				parent.repaint();
				parent.invokeAction();
			}

		}

	}

	/**
	 * The registered {@link TozenBoardMouseAdabter}
	 */
	private ArrayList<ActionListener> actionListener;
	/**
	 * Holds the tiles on the board:<br>
	 * board[x][y]
	 */
	private Tile board[][];
	private TozenBoard parent = this;
	/**
	 * The tile which is dragging at present. Null, if no tile is dragging.
	 */
	private Tile draggingTile;
	/**
	 * The origin of the tile which is dragging at the moment.
	 */
	private Point draggingTileOrigin = new Point();
	/**
	 * The last tried move.
	 */
	private Move lastTry = null;
	/**
	 * true, if the user can drag now<br>
	 * false, if the user can't drag now
	 */
	private boolean moveable;
	/**
	 * Restrict the drag&drop to one color. -1 for no ristriction.
	 */
	private int movingColor = -1;
	
	private boolean discoMode = false;
	private int discoModeSpeed = 300;
	private Thread discoThread = new Thread(new Runnable(){

		@Override
		public void run() {
			while(discoMode == true){
				try {
					Thread.sleep(discoModeSpeed);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				repaint();
			}
		}
		
	});
	public TozenBoard() {
		// Ititialisierung
		this.board = new Tile[12][12];

		this.setStartPosition();
		this.setSize(BOARD_SIZE, BOARD_SIZE);

		//Debug.initalize();
		
		// Events registrieren
		this.addMouseListener(new TozenBoardMouseAdabter());
		this.addMouseMotionListener(new TozenBoardMouseAdabter());
	}

	/**
	 * @param g2
	 *            Draws the tiles from {@link #board}
	 */
	private void drawTiles(Graphics2D g2) {
		for (int x = 0; x < 12; x++)
			for (int y = 0; y < 12; y++)
				if (this.board[x][y] != null) {
					if (this.board[x][y].color == this.movingColor) {
						g2.setColor(new Color(200, 0, 0, 100));
						g2.fill(new Rectangle2D.Double(x
								* (FIELD_SIZE + LINE_SIZE) + LINE_SIZE, y
								* (FIELD_SIZE + LINE_SIZE) + LINE_SIZE,
								FIELD_SIZE, FIELD_SIZE));
					}
					g2.drawImage(this.board[x][y].getImage(),
							x
									* (FIELD_SIZE + LINE_SIZE)
									+ LINE_SIZE
									+ ((FIELD_SIZE - this.board[x][y].tile
											.getSize()) / 2), y
									* (FIELD_SIZE + LINE_SIZE)
									+ LINE_SIZE
									+ ((FIELD_SIZE - this.board[x][y].tile
											.getSize()) / 2),
							this.board[x][y].tile.getSize(),
							this.board[x][y].tile.getSize(),
							new ImageObserver() {
								@Override
								public boolean imageUpdate(Image img,
										int infoflags, int x, int y, int width,
										int height) {

									return false;
								}
							});
				}

	}

	/**
	 * @param g2
	 *            Draws the tile which is dragging at the moment. And hightlight
	 *            the possible target field.
	 */
	private void drawDraggingTile(Graphics2D g2) {
		if (draggingTile != null) {
			int x = MouseInfo.getPointerInfo().getLocation().x
					- this.getLocationOnScreen().x;
			int y = MouseInfo.getPointerInfo().getLocation().y
					- this.getLocationOnScreen().y;

			y = y > BOARD_SIZE ? BOARD_SIZE : y < 0 ? 0 : y;
			x = x > BOARD_SIZE ? BOARD_SIZE : x < 0 ? 0 : x;

			Point field = parent.getFieldUnderPosition(x, y);
			g2.setColor(new Color(200, 200, 0, 150));
			g2.fill(new Rectangle2D.Double(field.x * (FIELD_SIZE + LINE_SIZE)
					+ LINE_SIZE,
					field.y * (FIELD_SIZE + LINE_SIZE) + LINE_SIZE, FIELD_SIZE,
					FIELD_SIZE));

			// Shadow
			g2.drawImage(draggingTile.getShadowImage(), (int) Math.round(x
					- (draggingTile.tile.getSize() * DRAGGING_ZOOM / 2)) + 5,
					(int) Math
							.round(y
									- (draggingTile.tile.getSize()
											* DRAGGING_ZOOM / 2)) + 15,
					(int) Math.round(draggingTile.tile.getSize()
							* DRAGGING_ZOOM),
					(int) Math.round(draggingTile.tile.getSize()
							* DRAGGING_ZOOM), this);

			// Tile
			g2.drawImage(draggingTile.getImage(), (int) Math.round(x
					- (draggingTile.tile.getSize() * DRAGGING_ZOOM / 2)),
					(int) Math
							.round(y
									- (draggingTile.tile.getSize()
											* DRAGGING_ZOOM / 2)),
					(int) Math.round(draggingTile.tile.getSize()
							* DRAGGING_ZOOM),
					(int) Math.round(draggingTile.tile.getSize()
							* DRAGGING_ZOOM), this);

		}
	}

	/**
	 * @param g2
	 *            Draw a little info, if the player can move
	 */
	public void drawInfo(Graphics2D g2) {
		if (this.moveable == true) {
			g2.setColor(new Color(50, 200, 0, 100));
			g2.fill(new Rectangle2D.Double(BOARD_SIZE / 2 - 75, 20, 150, 20));
			g2.setColor(Color.BLACK);
			g2.setFont(new Font("Serif", Font.BOLD, 18));
			g2.drawString("du bist am Zug...", BOARD_SIZE / 2 - 60, 35);

		} else {
			g2.setColor(new Color(200, 50, 0, 100));
			g2.fill(new Rectangle2D.Double(BOARD_SIZE / 2 - 75, 20, 150, 20));
			g2.setColor(Color.BLACK);
			g2.setFont(new Font("Serif", Font.BOLD, 18));
			g2.drawString("warte...", BOARD_SIZE / 2 - 30, 35);

		}
	}

	/**
	 * Gets the field cordiantes to all points on the playing field
	 * 
	 * @param x
	 *            the distance in pixel to the left side of the panel
	 * @param y
	 *            the distance in pixel to the top of the panel
	 * @return the coordinates of the field
	 */
	private Point getFieldUnderPosition(int x, int y) {
		return new Point(x / (FIELD_SIZE + LINE_SIZE), y
				/ (FIELD_SIZE + LINE_SIZE));
	}

	/**
	 * Just for testing...
	 */
	public void setStartPosition() {
		int p = 0;
		this.board[0][0] = new Tile(p, TileEnum.TOWER);
		this.board[0][1] = new Tile(p, TileEnum.ARCHER);
		this.board[0][2] = new Tile(p, TileEnum.ARCHER);
		this.board[0][3] = new Tile(p, TileEnum.ARCHER);
		this.board[1][0] = new Tile(p, TileEnum.ARCHER);
		this.board[2][0] = new Tile(p, TileEnum.ARCHER);
		this.board[3][0] = new Tile(p, TileEnum.ARCHER);
		this.board[1][1] = new Tile(p, TileEnum.KNIGHT);
		this.board[1][2] = new Tile(p, TileEnum.KNIGHT);
		this.board[2][1] = new Tile(p, TileEnum.KNIGHT);
		this.board[2][2] = new Tile(p, TileEnum.KNIGHT);
		p++;
		this.board[11][11] = new Tile(p, TileEnum.TOWER);
		this.board[11][10] = new Tile(p, TileEnum.ARCHER);
		this.board[11][9] = new Tile(p, TileEnum.ARCHER);
		this.board[11][8] = new Tile(p, TileEnum.ARCHER);
		this.board[10][11] = new Tile(p, TileEnum.ARCHER);
		this.board[9][11] = new Tile(p, TileEnum.ARCHER);
		this.board[8][11] = new Tile(p, TileEnum.ARCHER);
		this.board[10][10] = new Tile(p, TileEnum.KNIGHT);
		this.board[10][9] = new Tile(p, TileEnum.KNIGHT);
		this.board[9][10] = new Tile(p, TileEnum.KNIGHT);
		this.board[9][9] = new Tile(p, TileEnum.KNIGHT);
		p++;
		this.board[11][0] = new Tile(p, TileEnum.TOWER);
		this.board[11][1] = new Tile(p, TileEnum.ARCHER);
		this.board[11][2] = new Tile(p, TileEnum.ARCHER);
		this.board[11][3] = new Tile(p, TileEnum.ARCHER);
		this.board[10][0] = new Tile(p, TileEnum.ARCHER);
		this.board[9][0] = new Tile(p, TileEnum.ARCHER);
		this.board[8][0] = new Tile(p, TileEnum.ARCHER);
		this.board[10][1] = new Tile(p, TileEnum.KNIGHT);
		this.board[10][2] = new Tile(p, TileEnum.KNIGHT);
		this.board[9][1] = new Tile(p, TileEnum.KNIGHT);
		this.board[9][2] = new Tile(p, TileEnum.KNIGHT);
		p++;
		this.board[0][11] = new Tile(p, TileEnum.TOWER);
		this.board[0][10] = new Tile(p, TileEnum.ARCHER);
		this.board[0][9] = new Tile(p, TileEnum.ARCHER);
		this.board[0][8] = new Tile(p, TileEnum.ARCHER);
		this.board[1][11] = new Tile(p, TileEnum.ARCHER);
		this.board[2][11] = new Tile(p, TileEnum.ARCHER);
		this.board[3][11] = new Tile(p, TileEnum.ARCHER);
		this.board[1][10] = new Tile(p, TileEnum.KNIGHT);
		this.board[1][9] = new Tile(p, TileEnum.KNIGHT);
		this.board[2][10] = new Tile(p, TileEnum.KNIGHT);
		this.board[2][9] = new Tile(p, TileEnum.KNIGHT);
	}

	public void move(Point origin, Point target) {
		this.board[target.x][target.y] = this.board[origin.x][origin.y];
		this.board[origin.x][origin.y] = null;
		this.repaint();
	}

	/**
	 * Removes all tiles
	 */
	public void removeAllTiles() {
		this.board = new Tile[12][12];
	}

	/**
	 * Adds a tile to the board
	 * 
	 * @param x
	 *            the x coordinates
	 * @param y
	 *            the y coordiante
	 * @param p
	 *            the color
	 * @param t
	 *            the tile
	 */
	public void addTile(int x, int y, int c, TileEnum t)
			throws NullPointerException {
		this.board[x][y] = new Tile(c, t);
	}

	public void addActionListener(ActionListener a) {
		if (this.actionListener == null)
			this.actionListener = new ArrayList<ActionListener>();
		this.actionListener.add(a);
	}

	public void removeActionListener(ActionListener a) {
		this.actionListener.remove(a);
	}

	private void invokeAction() {
		for (ActionListener a : this.actionListener)
			a.actionPerformed(new ActionEvent(this, 1, "TRY_MOVE"));
	}

	/**
	 * Returns the last tried move
	 * 
	 * @return the move
	 */
	public Move getLastTry() {
		return lastTry;
	}

	/**
	 * @param m
	 *            true if the user can try a move
	 */
	public void setMoveable(boolean m) {
		if(m==true)
			(new AePlayWave("yourTurn.wav")).start();
		this.moveable = m;
		this.repaint();
	}

	/**
	 * Restrict the possible moves by a color
	 * 
	 * @param c
	 *            the color
	 */
	public void setMovingColor(int c) {
		//System.out.println(c);
		this.movingColor = c;
		this.repaint();
	}

	public int getMovingColor() {
		
		return this.movingColor;
	}

	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		// Draw backgorund
		BufferedImage bg = null;
		try {
			bg = Tools.loadImage("map_only.png");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		g2.drawImage(bg, 0, 0, BOARD_SIZE, BOARD_SIZE, new ImageObserver() {

			@Override
			public boolean imageUpdate(Image img, int infoflags, int x, int y,
					int width, int height) {
				// TODO Auto-generated method stub
				return false;
			}
		});

		drawTiles(g2);
		drawDraggingTile(g2);
		drawInfo(g2);
	}
	
	public void setDiscomode(boolean f){
		if(f == true){
			this.discoMode = true;
			this.discoThread.start();
		} else {
			this.discoMode = false;
		}
	}
	
	public void setDiscomode(boolean f, int s){
		if(f == true){
			this.discoMode = true;
			this.discoModeSpeed = s;
			this.discoThread.start();
		} else {
			this.discoMode = false;
		}
	}
	
	public void changeDiscomode(int s){
		this.discoModeSpeed = s;
		this.changeDiscomode();
			
	}
	
	public void changeDiscomode(){
		if(this.discoMode == false){
			this.setDiscomode(true);
		} else {
			this.setDiscomode(false);
		}
	}

	public static void main(String[] args) {
		JFrame f = new JFrame("Just Another Boring Board");
		f.setSize(469 + 8, 469 + 27);
		f.setVisible(true);
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		TozenBoard tb = new TozenBoard();
		f.add(tb);

		// Alle Figuren vom Feld löschen
		// tb.removeAllTiles();

		// Eine Figur auf das Spielbrett stellen
		tb.addTile(8 /* X */, 8/* X */, 1/* color: 1-4 */, TileEnum.ARCHER);
		tb.addTile(1, 1, 3, TileEnum.KNIGHT);

		// Einen Zug zulassen
		tb.setMoveable(true);
		int mc = 0;
		tb.setMovingColor(mc);
		tb.setDiscomode(true,50);
		
		tb.addActionListener(new ActionListener() {
			// Diese Methode wird aufgerufen, wenn der Spieler einen zug wagte.
			// Die Figur wird dann jedoch zurückgesetzt, damit der Spielzug
			// überprüft werden kann.
			@Override
			public void actionPerformed(ActionEvent e) {

				Move m = ((TozenBoard) e.getSource()).getLastTry();
				((TozenBoard) e.getSource()).move(m.origin, m.target);
				((TozenBoard) e.getSource()).setMovingColor((((TozenBoard) e
						.getSource()).getMovingColor() + 1) % 4);
			}
		});
	}

}
